package db;

import org.ricks.common.Pair;
import org.ricks.common.db.DaoSourceManage;
import org.ricks.db.DaoRuntimeException;
import org.ricks.log.Logger;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

/**
 * @author chenwei
 * @Description: 2022年9月9日 13:40:25 发现ORM存在BUG
 * @date 2020/12/1717:39
 */
public class PooledMgr {

    private static DataSource masterDataSource;
    private final ThreadLocal<ConnInfo> masterLocal = ThreadLocal.withInitial(ConnInfo::new);

    private static DataSource slaveDataSource;
    private final ThreadLocal<ConnInfo> slaveLocal = ThreadLocal.withInitial(ConnInfo::new);

    private static class SingleCase {
        public static final PooledMgr INSTANCE = new PooledMgr();
    }

    /**
     * 扫描APP目录找到DataSourceManage 实现类，开始init
     * @param clazz
     * @throws Exception
     */
    public static void init(Class<? extends DaoSourceManage> clazz)  {
        try {
            DaoSourceManage daoSourceManage = clazz.getConstructor().newInstance();
            Pair<DataSource,DataSource> pair = daoSourceManage.initDataSource();
            PooledMgr.masterDataSource = pair.getKey();
            PooledMgr.slaveDataSource = pair.getValue();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static PooledMgr get() {
        return SingleCase.INSTANCE;
    }

    public DataSource getMasterDataSource() {
        return masterDataSource;
    }

    public DataSource getSlaveDataSource() {
        return slaveDataSource;
    }

    public boolean isSlave() {
        return !Objects.isNull(slaveDataSource);
    }

    public PooledMgr() {
        try {
            if(masterDataSource == null) {
                Logger.error("检查有没有初始化DataSource！！！");
                throw new DaoRuntimeException("DataSource init failure, %s", getClass().getName());
            }
//            dataSource = new ComboPooledDataSource();
//            /**设置数据库连接驱动**/
//            dataSource.setDriverClass(JdbcConfig.getDriverClassName());
//            /**设置数据库连接地址**/
//            dataSource.setJdbcUrl(JdbcConfig.getUrl());
//            /**设置数据库连接用户名**/
//            dataSource.setUser(JdbcConfig.getUsername());
//            /**设置数据库连接密码**/
//            dataSource.setPassword(JdbcConfig.getPassword());
//            /**最大活跃连接数**/
//            dataSource.setMinPoolSize(50); //最小连接数
//            /** 连接池中保留的最大连接数。默认为15 **/
//            dataSource.setMaxPoolSize(50); //最大链接数
//            /** 最大空闲时间，超过空闲时间的连接将被丢弃。为0或负数则永不丢弃。默认为0 **/
//            dataSource.setMaxIdleTime(0);
//            /** 初始化时创建的连接数，应在minPoolSize与maxPoolSize之间取值。默认为3 **/
//            dataSource.setInitialPoolSize(50);
//            /**  定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个参数能显著提高测试速度。测试的表必须在初始数据源的时候就存在。默认为null **/
////            dataSource.setPreferredTestQuery(JdbcConfig.getPoolPingQuery());
//            /** 当连接池用完时客户端调用getConnection()后等待获取新连接的时间，超时后将抛出SQLException，如设为0则无限期等待。单位毫秒，默认为0 */
//            dataSource.setCheckoutTimeout(1000);
//
//            //c3p0官方文档给的解决方案，mysql5以上  autoReconnect=true 此种方案已经无效
//            /** 隔多少秒检查所有连接池中的空闲连接，默认为0表示不检查 */
//            dataSource.setIdleConnectionTestPeriod(10); // The last packet successfully received from the server was 6,503,493 milliseconds ago
//            /**   因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的时候都 将校验其有效性。建议使用 idleConnectionTestPeriod或automaticTestTable
//             等方法来提升连接测试的性能。默认为false  */
//            dataSource.setTestConnectionOnCheckout(false);
//            /** 如果设为true那么在取得连接的同时将校验连接的有效性。默认为false */
//            dataSource.setTestConnectionOnCheckin(true);
//
//
//            /** 当连接池中的连接用完时，C3P0一次性创建新连接的数目 */
//            dataSource.setAcquireIncrement(3);
//            /** 定义在从数据库获取新连接失败后重复尝试获取的次数，默认为30 */
//            dataSource.setAcquireRetryAttempts(0);
//            /** 两次连接中间隔时间，单位毫秒，默认为1000 */
//            dataSource.setAcquireRetryDelay(1000);
//            /** 连接关闭时默认将所有未提交的操作回滚。默认为false */
//            dataSource.setAutoCommitOnClose(false);
//            /** C3P0 将建一张名为Test的空表，并使用其自带的查询语句进行测试。如果定义了这个参数，那么属性preferredTestQuery将被忽略。你 不能在 这张Test表上进行任何操作，它将中为C3P0测试所用，默认为null */
////            dataSource.setAutomaticTestTable("test");
//            /** 获取 连接失败将会引起所有等待获取连接的线程抛出异常。但是数据源仍有效保留，并在下次调   用getConnection()的时候继续尝试获取连接。如 果设为true，那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。默认为 false */
//            dataSource.setBreakAfterAcquireFailure(false);
//
//            dataSource.setMaxStatements(0);
//
//            dataSource.setMaxStatementsPerConnection(0);
//
//            dataSource.setNumHelperThreads(10);


            this.masterDataSource = masterDataSource;
            this.slaveDataSource = slaveDataSource;

            //配置文件
//            Object hikariConfig = Class.forName("com.zaxxer.hikari.HikariConfig").getConstructor().newInstance();
//            Properties properties = new Properties();
//            properties.setProperty("cachePrepStmts", "true");
//            properties.setProperty("prepStmtCacheSize", "250");
//            properties.setProperty("prepStmtCacheSqlLimit", "2048");
//            Method jdbcUrl = hikariConfig.getClass().getMethod("setJdbcUrl",String.class);
//            jdbcUrl.invoke(hikariConfig,JdbcConfig.getUrl());
//
//            Method driverClassName = hikariConfig.getClass().getMethod("setDriverClassName",String.class);
//            driverClassName.invoke(hikariConfig,JdbcConfig.getDriverClassName());
//
//            Method username = hikariConfig.getClass().getMethod("setUsername",String.class);
//            username.invoke(hikariConfig,JdbcConfig.getUsername());
//
//            Method password = hikariConfig.getClass().getMethod("setPassword",String.class);
//            password.invoke(hikariConfig,JdbcConfig.getPassword());
//
//            Method propertiesM = hikariConfig.getClass().getMethod("setDataSourceProperties",Properties.class);
//            password.invoke(hikariConfig,propertiesM);

//            HikariConfig hikariConfig = new HikariConfig();
//            hikariConfig.setJdbcUrl(JdbcConfig.getUrl());//mysql
//            hikariConfig.setDriverClassName(JdbcConfig.getDriverClassName());
//            hikariConfig.setUsername(JdbcConfig.getUsername());
//            hikariConfig.setPassword(JdbcConfig.getPassword());
//            hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
//            hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
//            hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
////
////            masterDataSource = new HikariDataSource(hikariConfig);
//             masterDataSource = (DataSource) Class.forName("com.zaxxer.hikari.HikariDataSource").getConstructor(HikariConfig.class).newInstance(hikariConfig);
//             Logger.info("db已经加载........");
//             if(StringUtils.isNotBlank(JdbcConfig.getSlaveUrl())) {
//                 Logger.info("检测到db有从库,开始初始化从库........");
//                 hikariConfig.setJdbcUrl("jdbc:mysql://192.168.18.154:3306/demon?characterEncoding=utf8&serverTimezone=UTC&rewriteBatchedStatements=true&useSSL=true&autoReconnect=true&failOverReadOnly=false");
//                 hikariConfig.setUsername("root");
//                 hikariConfig.setPassword("123456");
//                 slaveDataSource = new HikariDataSource(hikariConfig);
//             }
         } catch (Exception e){
            e.printStackTrace();
        }
    }

    public Connection getConnection(boolean isMaster)  throws SQLException{
        if (isMaster || Objects.isNull(slaveDataSource)) {
            return getConnection(masterLocal,masterDataSource);
        } else {
            return getConnection(slaveLocal,slaveDataSource);
        }
    }

    public Connection getConnection (ThreadLocal<ConnInfo> local, DataSource dataSource ) throws SQLException{
        Connection conn = local.get().getConn();
        if (conn == null) {
            conn = dataSource.getConnection();
            if (local.get().isTransaction()) {
                conn.setAutoCommit(false);
            }
            local.get().setConn(conn);
        }
        Logger.error(conn.isValid(5000) + "   and   " + conn.toString());
        return conn;
    }

    private void discardConnection(ThreadLocal<ConnInfo> local) throws SQLException {
        Connection conn = local.get().getConn();
        if (conn != null) {
            conn.setAutoCommit(true);
            conn.close();
        }
        for (Connection otherConn : local.get().listOtherConn()) {
            otherConn.setAutoCommit(true);
            otherConn.close();
        }
        local.get().dispose();
    }

    public void discardConnectionFromDao(boolean isMaster) throws SQLException {
        if(isMaster || !isSlave()) {
            if (!masterLocal.get().isRemoteDiscard() && !masterLocal.get().isServiceDiscard()) {
                discardConnection(masterLocal);
            }
        } else {
            if (!slaveLocal.get().isRemoteDiscard() && !slaveLocal.get().isServiceDiscard()) {
                discardConnection(slaveLocal);
            }
        }
    }

    private static class ConnInfo {
        private boolean serviceDiscard;
        private boolean remoteDiscard;
        private Connection conn;
        private ConcurrentMap<String, Connection> otherConnMap = new ConcurrentHashMap<>();
        private boolean transaction;
        private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        public ConnInfo() {
            dispose();
        }
        public boolean isServiceDiscard() {
            return serviceDiscard;
        }
        public void setServiceDiscard(boolean serviceDiscard) {
            this.serviceDiscard = serviceDiscard;
        }
        public boolean isRemoteDiscard() {
            return remoteDiscard;
        }
        public void setRemoteDiscard(boolean remoteDiscard) {
            this.remoteDiscard = remoteDiscard;
        }
        public Connection getConn() {
            return conn;
        }
        public void setConn(Connection conn) {
            this.conn = conn;
        }
        public boolean isTransaction() {
            return transaction;
        }
        public void setTransaction(boolean transaction) {
            this.transaction = transaction;
        }
        public Connection getOtherConn(String topic) {
            return otherConnMap.get(topic);
        }
        public Connection putOtherConn(String topic, Connection conn) {
            return otherConnMap.putIfAbsent(topic, conn);
        }
        public Collection<Connection> listOtherConn() {
            return otherConnMap.values();
        }
        public void dispose() {
            serviceDiscard = false;
            remoteDiscard = false;
            conn = null;
            otherConnMap.clear();
            transaction = false;
        }
    }
}
